#include "graphics/utils.h"
#include "graphics/image.h"
#include "graphics/program_glsl.h"
#include "graphics/cube_texture_gl.h"
#include "graphics/image_texture_gl.h"
#include "graphics/error_gl.h"
#include "graphics/shader_object.h"
#include "geometry/matrix4x4.h"
#include "tools/obj_loader.h"
#include "shapes/triangle_mesh.h"
#include "shadow_volume_demo.h"
#include "nvModel/nvModel.h"
#include "nvModel/nvUtils.h"

namespace venus {
//bool *is_point_backfacing = NULL;
//bool *is_face_frontfacing = NULL;
//void PreCalculate(const float *vertices, int vert_count,
//                  const int *indices, int tri_count, const Point &light_pos) {
//  Utils::Delete(is_face_frontfacing);
//  Utils::Delete(is_point_backfacing);
//  is_point_backfacing = new bool[vert_count];
//  is_face_frontfacing = new bool[tri_count];
//  int scalar_idx = 0;
//  int p_idx = 0;
//  for (int t_idx = 0; t_idx < tri_count; ++t_idx) {
//    Point p[3];
//    for (int j = 0; j < 3; ++j) {
//      p[j].x = vertices[indices[scalar_idx++]];
//      p[j].y = vertices[indices[scalar_idx++]];
//      p[j].z = vertices[indices[scalar_idx++]];
//      is_point_backfacing[p_idx + j] = false;
//    }
//    is_face_frontfacing[t_idx] = false;
//    Vector3 e0 = p[1] - p[0];
//    Vector3 e1 = p[2] - p[0];
//    Vector3 N = Cross(e0, e1);
//    Vector3 to_light_dir = light_pos - p[0];
//    if (Dot(N, to_light_dir) > 0) {
//      is_face_frontfacing[t_idx] = true;
//    } else {
//      for (int j = 0; j < 3; ++j) {
//        is_point_backfacing[p_idx +j] = true;
//      }
//    }
//  }
//}
//void DrawInfiniteShadowVolume(nv::Model *model, const Point &light_pos, 
//                              bool draw_caps) {
//  int numTriangles = model->getCompiledIndexCount() / 3;
//  const float *vertices = model->getCompiledVertices();
//  const float *ptr = vertices;
//  glBegin(GL_QUADS);
//  {
//    for(unsigned int i=0; i<numTriangles; ++i) {
//      //if this face does not face the light, continue
//      if(!is_face_frontfacing[i]) continue;
//
//      //Loop through edges on this face
//      for(int j=0; j<3; ++j)
//      {
//        //Draw the shadow volume "edge" if this is a silhouette edge
//        if(isSilhouetteEdge[i*3+j])
//        {
//          VECTOR3D vertex1=vertices[indices[i*3+j]].position;
//          VECTOR3D vertex2=vertices[indices[i*3+(j+1)%3]].position;
//
//          glVertex3fv(vertex2);
//          glVertex3fv(vertex1);
//          glVertex4f(	vertex1.x-lightPosition.x,
//            vertex1.y-lightPosition.y,
//            vertex1.z-lightPosition.z, 0.0f);
//          glVertex4f(	vertex2.x-lightPosition.x,
//            vertex2.y-lightPosition.y,
//            vertex2.z-lightPosition.z, 0.0f);
//        }
//      }
//    }
//  }
//  glEnd();
//
//  //Draw caps if required
//  if(draw_caps)
//  {
//    glBegin(GL_TRIANGLES);
//    {
//      for(unsigned int i=0; i<numTriangles; ++i)
//      {
//        for(int j=0; j<3; ++j)
//        {
//          VECTOR3D vertex=vertices[indices[i*3+j]].position;
//
//          if(isFacingLight[i])
//            glVertex3fv(vertex);
//          else
//            glVertex4f(	vertex.x-lightPosition.x,
//            vertex.y-lightPosition.y,
//            vertex.z-lightPosition.z, 0.0f);
//        }
//      }
//    }
//    glEnd();
//  }
//}
ShadowVolumeDemo::ShadowVolumeDemo() 
    : GlutWindow("Ray Marching Demo",10, 10, 800, 600, 
                 GLUT_RGBA|GLUT_DOUBLE|GLUT_DEPTH|GLUT_STENCIL),
      camera_(Point(0, 0, -5), Point(0, 0, 0)), light_(Point(5, 5, 5)) {
  camera_.set_fovy(40.0);
  camera_.set_z_far(1000.0);
  camera_.set_rotation_speed(10.f);
  camera_.set_velocity(0.5, 0.5, 0.5);
  left_button_down_ = false;
  right_button_down_ = false;
  show_shadow_volume_ = false;
  Utils::SetNULL(sky_box_, 6);
  Utils::SetNULL(model_tex_);
  Utils::SetNULL(model_);
  Utils::SetNULL(model2_);
  Utils::SetNULL(plane_tex);
  Utils::SetNULL(plane_);
  Utils::SetNULL(lit_scene_program_);
  Utils::SetNULL(ambient_scene_program_);
}
ShadowVolumeDemo::~ShadowVolumeDemo() {
  Utils::Delete(sky_box_, 6);
  Utils::Delete(model_tex_);
  Utils::Delete(model_);
  Utils::Delete(model2_);
  Utils::Delete(plane_tex);
  Utils::Delete(plane_);
  Utils::Delete(lit_scene_program_);
  Utils::Delete(ambient_scene_program_);
}
bool ShadowVolumeDemo::Initialize() {
  glewInit();
  if (!glewIsSupported( "GL_VERSION_2_0 GL_EXT_framebuffer_object")) {
    printf( "Required extensions not supported.\n");
    return false;
  }
 
  model_ = new nv::Model;
  model_->loadModelFromFile("../models/cow.obj");
  model_->removeDegeneratePrims();
  model_->computeNormals();
  model_->compileModel(nv::Model::eptTrianglesWithAdjacency);

  model2_ = new nv::Model;
  model2_->loadModelFromFile("../models/teapot.obj");
  model2_->removeDegeneratePrims();
  model2_->computeNormals();
  model2_->compileModel(nv::Model::eptTrianglesWithAdjacency);

  plane_ = new nv::Model;
  plane_->loadModelFromFile("../models/plane.obj");
  plane_->computeNormals();
  plane_->compileModel(nv::Model::eptTrianglesWithAdjacency);

  V_RET(this->LoadShaders());
  V_RET(this->LoadTextures());

  glEnable(GL_DEPTH_TEST);

  for (int i = 0; i < light_count_; ++i) {
    lights_[i] = new FlyMotion(Point(5, 5, 5), Point(5, 5, 0));
  }
  return true;
}

void ShadowVolumeDemo::OnDisplay() {
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  camera_.ApplyViewTransform();
  glGetFloatv(GL_MODELVIEW_MATRIX, mv_mat_);

  this->AmbientPass();

  //set shadow_vol_program parameters
  shadow_vol_program_->Begin();
  glUniformMatrix4fv(proj_mat_var_, 1, GL_FALSE, proj_mat_);
  glUniformMatrix4fv(mv_mat_var_, 1, GL_FALSE, mv_mat_);
  glUniform1f(extrude_bias_var_, 0.005);
  glUniform1f(extrude_extent_var_, 100.0 - 0.1);
  shadow_vol_program_->End();
  for (int i = 0; i < light_count_; ++i) {
    //draw the light
    glPushMatrix();
    glTranslatef(lights_[i]->position().x, lights_[i]->position().y, 
                 lights_[i]->position().z);
    glutSolidSphere(0.2, 20, 20);
    glPopMatrix();
    // render shadow volume
    shadow_vol_program_->Begin();
    glUniform3fv(light_pos_var_, 1, lights_[i]->position().ptr());
    if (show_shadow_volume_) RenderScene(true);
    else  ShadowPass();
    shadow_vol_program_->End();
    this->IlluminationPass();
  }
  //this->VisualizeStencil();
  FinalPass();

  DisplayStastics("frame rate", fps_counter_.Update(), "fps");
  glutSwapBuffers();
}

void ShadowVolumeDemo::OnResize(int width, int height) {
  this->set_width(width);
  this->set_height(height);

  glViewport(0, 0, width, height);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  camera_.set_aspect_ratio(static_cast<double>(width)/height);
  camera_.ApplyProjectionTransform();
  glGetFloatv(GL_PROJECTION_MATRIX, proj_mat_);
  glMatrixMode(GL_MODELVIEW);
}
void ShadowVolumeDemo::OnIdle() {
  this->ReDraw();
}
void ShadowVolumeDemo::OnKeyboard(unsigned char key, int x, int y) {
  switch (key) {
    case 'a':
      camera_.Yaw(-1);
      break;
    case 'd':
      camera_.Yaw(1);
      break;
    case 'w':
      camera_.Walk(1);
      break;
    case 's':
      camera_.Walk(-1);
      break;
    case 'q':
      camera_.Pitch(1);
      break;
    case 'z':
      camera_.Pitch(-1);
      break;
    case 'v':
      show_shadow_volume_ = !show_shadow_volume_;
      break;
  }
}

void ShadowVolumeDemo::OnSpecialKey(int key, int x, int y) {
  switch(key) {
  case GLUT_KEY_UP:
    camera_.Fly(1);
    break;
  case GLUT_KEY_DOWN:
    camera_.Fly(-1);
    break;
  case GLUT_KEY_LEFT:
    camera_.Strafe(-1);
    break;
  case GLUT_KEY_RIGHT:
    camera_.Strafe(1);
    break;
  default:
    break;
  }
}

void ShadowVolumeDemo::OnMouse(int button, int state, int x, int y) {
  if (GLUT_LEFT_BUTTON == button) {
    if (GLUT_DOWN == state) { 
      left_button_down_ = true;
    } else {
      left_button_down_ = false;
    }
  } else if(GLUT_RIGHT_BUTTON == button) {
    if (GLUT_DOWN == state) {
      right_button_down_ = true;
    } else {
      right_button_down_ = false;
    }
  }
  last_mouse_pos_.x = x; last_mouse_pos_.y = y;
}
void ShadowVolumeDemo::OnMouseMove(int x, int y) {
  static float step = 0.1;
  if (left_button_down_) {
    lights_[0]->Strafe((last_mouse_pos_.x - x) * step);
    lights_[0]->Fly((last_mouse_pos_.y - y) * step);
  }
  if (right_button_down_) {
    lights_[0]->Walk((y - last_mouse_pos_.y) * step);
  }
  last_mouse_pos_.x = x; last_mouse_pos_.y = y;
}


void ShadowVolumeDemo::RenderSkyBox() {
  glEnable(GL_TEXTURE_2D);
  float s = 50.f;
  //look from inside the box!!! 
  //specify the box first, then rotate the texture
  sky_box_[0]->Bind();
  glBegin(GL_QUADS);
  glTexCoord2f(0, 0);
  glVertex3f(-s, -s, -s);
  glTexCoord2f(1, 0);
  glVertex3f(+s, -s, -s);
  glTexCoord2f(1, 1);
  glVertex3f(+s, +s, -s);
  glTexCoord2f(0, 1);
  glVertex3f(-s, +s, -s);
  glEnd();

  sky_box_[1]->Bind();
  glBegin(GL_QUADS);
  glTexCoord2f(0, 0);
  glVertex3f(-s, -s, +s);
  glTexCoord2f(1, 0);
  glVertex3f(-s, -s, -s);
  glTexCoord2f(1, 1);
  glVertex3f(-s, +s, -s);
  glTexCoord2f(0, 1);
  glVertex3f(-s, +s, +s);
  glEnd();


  sky_box_[2]->Bind();
  glBegin(GL_QUADS);
  glTexCoord2f(0, 0);
  glVertex3f(+s, -s, +s);
  glTexCoord2f(1, 0);
  glVertex3f(-s, -s, +s);
  glTexCoord2f(1, 1);
  glVertex3f(-s, +s, +s);
  glTexCoord2f(0, 1);
  glVertex3f(+s, +s, +s);
  glEnd();

  sky_box_[3]->Bind();
  glBegin(GL_QUADS);
  glTexCoord2f(0, 0);
  glVertex3f(+s, -s, -s);
  glTexCoord2f(1, 0);
  glVertex3f(+s, -s, +s);
  glTexCoord2f(1, 1);
  glVertex3f(+s, +s, +s);
  glTexCoord2f(0, 1);
  glVertex3f(+s, +s, -s);
  glEnd();

  sky_box_[4]->Bind();
  glBegin(GL_QUADS);
  glTexCoord2f(0, 1);
  glVertex3f(-s, +s, -s);
  glTexCoord2f(0, 0);
  glVertex3f(+s, +s, -s);
  glTexCoord2f(1, 0);
  glVertex3f(+s, +s, +s);
  glTexCoord2f(1, 1);
  glVertex3f(-s, +s, +s);
  glEnd();

  sky_box_[5]->Bind();
  glBegin(GL_QUADS);
  glTexCoord2f(0, 1);
  glVertex3f(+s, -s, -s);
  glTexCoord2f(0, 0);
  glVertex3f(-s, -s, -s);
  glTexCoord2f(1, 0);
  glVertex3f(-s, -s, +s);
  glTexCoord2f(1, 1);
  glVertex3f(+s, -s, +s);
  glEnd();
  sky_box_[5]->Unbind();
  glDisable(GL_TEXTURE_2D);
}

void ShadowVolumeDemo::RenderScene(bool shadow_pass) {
  if (!shadow_pass) this->RenderSkyBox();
  glPushMatrix();
  glTranslatef(0, -0.2, 0);
  if (!shadow_pass) {
    plane_tex->Enable();
    plane_tex->Bind();
    nv::DrawTriangles(plane_);
    plane_tex->Unbind();
    plane_tex->Disable();
  } else {
    nv::DrawWithAdjacency(plane_);
  }
  glPopMatrix();

  // draw models
  glPushMatrix();
  glTranslatef(-0.5, 0.3, -0.5);
  glScalef(0.1, 0.1, 0.1);
  if (!shadow_pass) {
    model_tex_->Enable();
    model_tex_->Bind();
    nv::DrawTriangles(model_);
    model_tex_->Unbind();
    model_tex_->Disable();
  } else {
    nv::DrawWithAdjacency(model_);
  }
  glPopMatrix();

  // draw models
  glPushMatrix();
  glTranslatef(0.5, 0, 0.0);
  glScalef(0.2, 0.2, 0.2);
  if (!shadow_pass) {
    model_tex_->Enable();
    model_tex_->Bind();
    nv::DrawTriangles(model2_);
    model_tex_->Unbind();
    model_tex_->Disable();
  } else {
    nv::DrawWithAdjacency(model2_);
  }
  glPopMatrix();
}

void ShadowVolumeDemo::UpdateLight(int i) {
  glUniform3fv(light_pos_var_, 1, lights_[i]->position().ptr());
  float light_color[4] = { 1, 1, 1, 1 };
  glUniform4fv(light_diffuse_var_, 1, light_color);
}
void ShadowVolumeDemo::AmbientPass() {
  glEnable(GL_DEPTH_TEST);
  //glEnable(GL_CULL_FACE);
  ambient_scene_program_->Begin();
  RenderScene(false);
  ambient_scene_program_->End();
  glDisable(GL_CULL_FACE);
  glDisable(GL_DEPTH_TEST);
}
void ShadowVolumeDemo::ShadowPass() {
  glPushAttrib(GL_ALL_ATTRIB_BITS);
  glDisable(GL_BLEND);
  glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);

  glEnable(GL_DEPTH_TEST);
  glDepthFunc(GL_LEQUAL);
  glDepthMask(0);

  glEnable(GL_STENCIL_TEST);
  glEnable(GL_STENCIL_TEST_TWO_SIDE_EXT);
  glActiveStencilFaceEXT(GL_BACK);
  glStencilMask(~0);
  glStencilFunc(GL_ALWAYS, 0, ~0);
  glStencilOp(GL_KEEP, GL_KEEP, GL_DECR_WRAP_EXT);
  glActiveStencilFaceEXT(GL_FRONT);
  glStencilMask(~0);
  glStencilFunc(GL_ALWAYS, 0, ~0);
  glStencilOp(GL_KEEP, GL_KEEP, GL_INCR_WRAP_EXT);

  glDisable(GL_CULL_FACE);

  RenderScene(true);
  glPopAttrib();
}
void ShadowVolumeDemo::IlluminationPass() {
  glPushAttrib(GL_ALL_ATTRIB_BITS);

  glEnable(GL_STENCIL_TEST);          // read from stencil buffer
  glStencilMask(0);                   // do not write to stencil buffer
  glStencilFunc(GL_EQUAL, 0, 0xff);   // set stencil test function
  glColorMask(1,1,1,1);
  glEnable(GL_BLEND);         // add light contribution to frame buffer
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 // glBlendFunc(GL_ONE, GL_ONE);
  glEnable(GL_CULL_FACE);             // enable face culling
  glEnable(GL_DEPTH_TEST);
  glDepthMask(0);                     // do not write to z-buffer
  glDepthFunc(GL_LEQUAL);
 
  lit_scene_program_->Begin();
  RenderScene(false);
  lit_scene_program_->End();
  glPopAttrib();
}
void ShadowVolumeDemo::FinalPass() {
}
bool ShadowVolumeDemo::LoadShaders() {
  lit_scene_program_ = new ProgramGLSL;
  lit_scene_program_->Load("shaders/scene.vert.glsl",  
                           "shaders/lit_scene.frag.glsl");
  lit_scene_program_->Link();
  tex_var_ = lit_scene_program_->GetUniformVar("tex");
  lit_scene_program_->Begin();
  glUniform1i(tex_var_, 0);
  lit_scene_program_->End();

  ambient_scene_program_ = new ProgramGLSL;
  ambient_scene_program_->Load("shaders/scene.vert.glsl",  
                            "shaders/ambient_scene.frag.glsl");
  ambient_scene_program_->Link();
  tex_var_ = ambient_scene_program_->GetUniformVar("tex");
  ambient_scene_program_->Begin();
  glUniform1i(tex_var_, 0);
  ambient_scene_program_->End();

  shadow_vol_program_ = new ProgramGLSL;
  shadow_vol_program_->Load("shaders/shadow_vol.vert.glsl", 
                            "shaders/shadow_vol.frag.glsl");
  ShaderObject geom_shader(GL_GEOMETRY_SHADER, "shaders/shadow_vol.geom.glsl",
                           FILE_SOURCE);
  shadow_vol_program_->AttachShader(geom_shader);
  shadow_vol_program_->Link();
  light_pos_var_ = shadow_vol_program_->GetUniformVar("light_pos");
  extrude_extent_var_ = shadow_vol_program_->GetUniformVar("extrude_extent");
  extrude_bias_var_ = shadow_vol_program_->GetUniformVar("extrude_bias");
  proj_mat_var_ = shadow_vol_program_->GetUniformVar("proj_mat");
  mv_mat_var_ = shadow_vol_program_->GetUniformVar("mv_mat");

  return true;
}
bool ShadowVolumeDemo::LoadTextures() {
  const char *tex_names[6] = { "../media/skybox/bk.jpg", 
    "../media/skybox/lt.jpg", 
    "../media/skybox/ft.jpg", 
    "../media/skybox/rt.jpg", 
    "../media/skybox/tp.jpg", 
    "../media/skybox/dn.jpg" };
  Image img;
  for (int i = 0; i < 6; ++i) {
    V_RET(img.Load(tex_names[i]));
    sky_box_[i] = new ImageTextureGL;
    sky_box_[i]->Initialize(img, GL_RGBA);
  }
  img.Load("../media/textures/3.jpg");
  model_tex_ = new ImageTextureGL;
  model_tex_->Initialize(img, GL_RGBA);
  plane_tex = new ImageTextureGL;
  img.Load("../media/textures/wood1.jpg");
  plane_tex->Initialize(img, GL_RGBA, kUseMipmaps);
  plane_tex->SetParameter(GL_TEXTURE_WRAP_S, GL_REPEAT);
  plane_tex->SetParameter(GL_TEXTURE_WRAP_T, GL_REPEAT);
  plane_tex->SetParameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR); 
  plane_tex->SetParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); 
  return true;
}

void ShadowVolumeDemo::VisualizeStencil() {
  glPushAttrib(GL_ALL_ATTRIB_BITS);
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();
  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  glDisable(GL_LIGHTING);
  glDisable(GL_DEPTH_TEST);
  glEnable(GL_STENCIL_TEST);
  glColor3f(1.0f, 1.0f, 1.0f);
  glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

  /* Copy bit 0 into the blue plane */
  glColorMask(GL_FALSE, GL_FALSE, GL_TRUE, GL_FALSE);
  glStencilFunc(GL_NOTEQUAL, 0, 0xf8 | 0x01);
  glRectf(-1.0f, -1.0f, 1.0f, 1.0f);

  /* Copy bit 1 into the red plane */
  glColorMask(GL_TRUE, GL_FALSE, GL_FALSE, GL_FALSE);
  glStencilFunc(GL_NOTEQUAL, 0, 0xf8 | 0x02);
  glRectf(-1.0f, -1.0f, 1.0f, 1.0f);

  /* Copy bit 2 into the green plane */
  glColorMask(GL_FALSE, GL_TRUE, GL_FALSE, GL_FALSE);
  glStencilFunc(GL_NOTEQUAL, 0, 0xf8 | 0x04);
  glRectf(-1.0f, -1.0f, 1.0f, 1.0f);
  glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_LIGHTING);
  glMatrixMode(GL_PROJECTION);
  glPopMatrix();
  glMatrixMode(GL_MODELVIEW);
  glPopMatrix();
  glPopAttrib();
}

} // namespace venus
